//
//  TextLabel.swift
//  LibUI
//
//  Created by ChangtanSun on 2017/7/6.
//  Copyright © 2017年 ChangtanSun. All rights reserved.
//

import UIKit

public class TextLabel: UILabel {
    var textContainer: NSTextContainer
    var textStorage: NSTextStorage?
    let textLayoutManager = TextLayoutManager()

    public var contentInsets: UIEdgeInsets

    public init(frame: CGRect = .zero, contentInsets: UIEdgeInsets = .zero) {
        textContainer = NSTextContainer(size: CGSize(width: 100, height: 40))
        textContainer.lineBreakMode = .byTruncatingTail
        textContainer.lineFragmentPadding = 0

        self.contentInsets = contentInsets

        super.init(frame: frame)

        textLayoutManager.addTextContainer(textContainer)
    }

    required public init?(coder aDecoder: NSCoder) {
        textContainer = NSTextContainer()

        contentInsets = .zero

        super.init(coder: aDecoder)

        textLayoutManager.addTextContainer(textContainer)
    }

    public var lineFragmentPadding: CGFloat {
        get {
            return textContainer.lineFragmentPadding
        }

        set {
            let textContainer = NSTextContainer(size: self.textContainer.size)
            textContainer.lineBreakMode = .byTruncatingTail
            textContainer.lineFragmentPadding = newValue
            textContainer.maximumNumberOfLines = self.textContainer.maximumNumberOfLines
            textLayoutManager.removeTextContainer(at: 0)
            self.textContainer = textContainer
            textLayoutManager.addTextContainer(textContainer)
        }
    }

    public var onLinkClick: ((_ link: Any?) -> Void)? {
        didSet {
            if onLinkClick != nil {
                isUserInteractionEnabled = true
            } else {
                isUserInteractionEnabled = false
            }
        }
    }

    public override var attributedText: NSAttributedString? {
        set {
            guard let text = newValue else {
                super.attributedText = nil
                textStorage = nil
                return
            }

            textStorage = NSTextStorage(attributedString: text)
            textStorage?.addLayoutManager(textLayoutManager)

            super.attributedText = textStorage
        }

        get {
            return textStorage
        }
    }

    public override var text: String? {
        didSet {
            onLinkClick = nil
            textStorage = nil
        }
    }

    public override var numberOfLines: Int {
        didSet {
            textContainer.maximumNumberOfLines = numberOfLines
        }
    }

    func calcGlyphsPositionInView() -> CGPoint {
        return CGPoint.zero
    }

    override public func drawText(in rect: CGRect) {
        if attributedText != nil {

            let glyphRange = textLayoutManager.glyphRange(for: textContainer)
            let glyphsPosition = calcGlyphsPositionInView()

            textLayoutManager.drawBackground(forGlyphRange: glyphRange, at: glyphsPosition)
            textLayoutManager.drawGlyphs(forGlyphRange: glyphRange, at: glyphsPosition)
        } else {
            super.drawText(in: rect.inset(by: contentInsets))
        }
    }

    public override func layoutSubviews() {
        guard let length = textStorage?.length, length > 0 else {
            return
        }

        if let paragraphStyle = textStorage?.attribute(NSAttributedString.Key.paragraphStyle, at: length - 1, effectiveRange: nil) as? NSParagraphStyle {
            textContainer.size = CGSize(width: bounds.width, height: bounds.height + paragraphStyle.lineSpacing)
        } else {
            textContainer.size = bounds.size
        }
    }

    open override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        guard textStorage != nil else {
            return super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
        }

        let savedTextContainerSize = textContainer.size
        let savedTextContainerNumberOfLines = textContainer.maximumNumberOfLines

        textContainer.size = bounds.size

        textContainer.maximumNumberOfLines = numberOfLines

        var textBounds = CGRect.zero

        let glyphRange = textLayoutManager.glyphRange(for: textContainer)
        textBounds = textLayoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)

        textBounds.origin = bounds.origin
        textBounds.size.width = ceil(textBounds.size.width)
        textBounds.size.height = ceil(textBounds.size.height)

        textContainer.size = savedTextContainerSize
        textContainer.maximumNumberOfLines = savedTextContainerNumberOfLines

        return textBounds
    }

    override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        guard let textStorage = self.textStorage else {
            return super.hitTest(point, with: event)
        }

        var view: UIView?

        textStorage.enumerateAttribute(NSAttributedString.Key.textLink, in: NSRange(location: 0, length: textStorage.length), options: [.longestEffectiveRangeNotRequired], using: { (any, range, _) in
            guard any != nil else {
                return
            }

            let glyphRange = textLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
            let rect = textLayoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
            let insetRect = rect.insetBy(dx: -5, dy: -5)
            if insetRect.contains(point) {
                view = self
                return
            }
        })

        return view
    }

    open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    }

    open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {

    }

    open override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {

    }

    open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let textStorage = self.textStorage else {
            return
        }

        guard let touch = touches.first else {
            return
        }

        let point = touch.location(in: self)

        textStorage.enumerateAttribute(NSAttributedString.Key.textLink, in: NSRange(location: 0, length: textStorage.length), options: [.longestEffectiveRangeNotRequired], using: { (any, range, _) in
            guard any != nil else {
                return
            }

            let glyphRange = textLayoutManager.glyphRange(forCharacterRange: range, actualCharacterRange: nil)
            let rect = textLayoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)

            if rect.contains(point), let onLinkClick = onLinkClick {
                onLinkClick(any)
            }
        })
    }
}
